package com.eedi.framework.permission.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.excel.EasyExcel;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.eedi.framework.permission.controller.org.vo.role.*;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import com.google.common.collect.Sets;
import com.eedi.framework.common.enums.CommonStatusEnum;
import com.eedi.framework.common.enums.YesOrNoEnum;
import com.eedi.framework.common.exception.util.PreconditionsUtils;
import com.eedi.framework.common.pojo.PageResult;
import com.eedi.framework.common.util.collection.CollectionUtils;
import com.eedi.framework.datapermission.core.annotation.DataPermission;
import com.eedi.framework.file.core.client.FileClient;
import com.eedi.framework.file.core.enums.MimeTypeEnum;
import com.eedi.framework.file.core.utils.FileTypeUtils;
import com.eedi.framework.permission.controller.org.vo.OrgDeptDataPermissionRespDTO;
import com.eedi.framework.permission.controller.org.vo.menu.OrgMenuListReq;
import com.eedi.framework.permission.controller.org.vo.menu.OrgMenuSaveReq;
import com.eedi.framework.permission.controller.org.vo.permission.OrgPermissionAssignUserRoleImportFailResp;
import com.eedi.framework.permission.controller.org.vo.permission.OrgPermissionAssignUserRoleRowReq;
import com.eedi.framework.permission.controller.org.vo.permission.OrgPermissionBatchAssignUserRoleResp;
import com.eedi.framework.permission.controller.org.vo.role.*;
import com.eedi.framework.permission.convert.OrgPermissionAssign;
import com.eedi.framework.permission.dal.OrgPermissionCacheKeyConstants;
import com.eedi.framework.permission.dal.dataobject.OrgMenuDO;
import com.eedi.framework.permission.dal.dataobject.OrgRoleDO;
import com.eedi.framework.permission.dal.dataobject.OrgRoleMenuDO;
import com.eedi.framework.permission.dal.dataobject.OrgUserRoleDO;
import com.eedi.framework.permission.dal.mapper.OrgRoleMapper;
import com.eedi.framework.permission.dal.mapper.OrgRoleMenuMapper;
import com.eedi.framework.permission.dal.mapper.OrgUserRoleMapper;
import com.eedi.framework.permission.enums.OrgDataScopeEnum;
import com.eedi.framework.permission.enums.OrgPermissionErrorCodeConstants;
import com.eedi.framework.permission.enums.OrgRoleCodeEnum;
import com.eedi.framework.permission.excel.ExcelListener;
import com.eedi.framework.permission.facade.OrgPermissionFacade;
import com.eedi.framework.template.controller.sys.vo.permission.SysTemplateRoleMenuResp;
import com.eedi.framework.template.service.SysTemplatePermissionService;
import com.eedi.org.facade.OrgUserFacade;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static com.eedi.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.eedi.framework.common.util.collection.CollectionUtils.convertSet;
import static com.eedi.framework.common.util.json.JsonUtils.toJsonString;
import static com.eedi.framework.permission.enums.OrgPermissionErrorCodeConstants.ORG_TEMPLATE_ROLE_MENU_IS_NULL;

/**
 * 权限 Service 实现类
 *
 * @author 永聚长青源码
 */
@Service
@Slf4j
public class OrgPermissionServiceImpl implements OrgPermissionService {

    @Resource
    private OrgRoleMenuMapper orgRoleMenuMapper;
    @Resource
    private OrgUserRoleMapper orgUserRoleMapper;
    @Resource
    private OrgRoleMapper orgRoleMapper;

    @Resource
    private OrgRoleService orgRoleService;
    @Resource
    private OrgMenuService orgMenuService;
    @Resource
    private OrgPermissionFacade orgPermissionFacade;
    @Resource
    private OrgUserFacade orgUserFacade;
    @Resource
    private FileClient fileClientPrivate;
    @Resource
    private SysTemplatePermissionService sysTemplatePermissionService;

    /**
     * 批量为成员添加角色权限的excel模板
     */
    private final String TEMPLATE_EXCEL = "用户角色批量导入模板.xlsx";

    @Override
    public boolean hasAnyPermissions(String userId, String... permissions) {
        // 如果为空，说明已经有权限
        if (ArrayUtil.isEmpty(permissions)) {
            return true;
        }

        // 获得当前登录的角色。如果为空，说明没有权限
        List<OrgRoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
        if (CollUtil.isEmpty(roles)) {
            return false;
        }

        // 情况一：遍历判断每个权限，如果有一满足，说明有权限
        for (String permission : permissions) {
            if (hasAnyPermission(roles, permission)) {
                return true;
            }
        }

        // 情况二：如果是超管，也说明有权限
        return orgRoleService.hasAnySuperAdmin(convertSet(roles, OrgRoleDO::getOrgRoleId));
    }

    /**
     * 判断指定角色，是否拥有该 permission 权限
     *
     * @param roles      指定角色数组
     * @param permission 权限标识
     * @return 是否拥有
     */
    private boolean hasAnyPermission(List<OrgRoleDO> roles, String permission) {
        List<String> menuIds = orgMenuService.getMenuIdListByPermissionFromCache(permission);
        // 采用严格模式，如果权限找不到对应的 Menu 的话，也认为没有权限
        if (CollUtil.isEmpty(menuIds)) {
            return false;
        }

        // 判断是否有权限
        Set<String> roleIds = convertSet(roles, OrgRoleDO::getOrgRoleId);
        for (String menuId : menuIds) {
            // 获得拥有该菜单的角色编号集合
            Set<String> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache(menuId);
            // 如果有交集，说明有权限
            if (CollUtil.containsAny(menuRoleIds, roleIds)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean hasAnyRoles(String userId, String... roles) {
        // 如果为空，说明已经有权限
        if (ArrayUtil.isEmpty(roles)) {
            return true;
        }

        // 获得当前登录的角色。如果为空，说明没有权限
        List<OrgRoleDO> roleList = getEnableUserRoleListByUserIdFromCache(userId);
        if (CollUtil.isEmpty(roleList)) {
            return false;
        }

        // 判断是否有角色
        Set<String> userRoles = convertSet(roleList, OrgRoleDO::getOrgRoleCode);
        return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
    }

    // ========== 角色-菜单的相关方法  ==========

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    @CacheEvict(value = OrgPermissionCacheKeyConstants.MENU_ROLE_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，主要一次更新涉及到的 menuIds 较多，反倒批量会更快
    public void assignRoleMenu(String roleId, Set<String> menuIds) {
        // 获得角色拥有菜单编号
        Set<String> dbMenuIds = convertSet(orgRoleMenuMapper.selectListByRoleId(roleId), OrgRoleMenuDO::getOrgMenuId);
        // 计算新增和删除的菜单编号
        Set<String> menuIdList = CollUtil.emptyIfNull(menuIds);
        Collection<String> createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds);
        Collection<String> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList);
        // 执行新增和删除。对于已经授权的菜单，不用做任何处理
        if (CollUtil.isNotEmpty(createMenuIds)) {
            orgRoleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
                OrgRoleMenuDO entity = new OrgRoleMenuDO();
                entity.setOrgRoleId(roleId);
                entity.setOrgMenuId(menuId);
                return entity;
            }));
        }
        if (CollUtil.isNotEmpty(deleteMenuIds)) {
            orgRoleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Caching(evict = {
            @CacheEvict(value = OrgPermissionCacheKeyConstants.MENU_ROLE_ID_LIST,
                    allEntries = true), // allEntries 清空所有缓存，此处无法方便获得 roleId 对应的 menu 缓存们
            @CacheEvict(value = OrgPermissionCacheKeyConstants.USER_ROLE_ID_LIST,
                    allEntries = true) // allEntries 清空所有缓存，此处无法方便获得 roleId 对应的 user 缓存们
    })
    public void processRoleDeleted(String roleId) {
        // 标记删除 UserRole
        orgUserRoleMapper.deleteListByRoleId(roleId);
        // 标记删除 RoleMenu
        orgRoleMenuMapper.deleteListByRoleId(roleId);
    }

    @Override
    @CacheEvict(value = OrgPermissionCacheKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
    public void processMenuDeleted(String menuId) {
        orgRoleMenuMapper.deleteListByMenuId(menuId);
    }

    @Override
    public Set<String> getRoleMenuListByRoleId(Collection<String> roleIds) {
        if (CollUtil.isEmpty(roleIds)) {
            return Collections.emptySet();
        }

        // 如果是管理员的情况下，获取全部菜单编号
        if (orgRoleService.hasAnySuperAdmin(roleIds)) {
            return convertSet(orgMenuService.getMenuList(), OrgMenuDO::getOrgMenuId);
        }
        // 如果是非管理员的情况下，获得拥有的菜单编号
        return convertSet(orgRoleMenuMapper.selectListByRoleId(roleIds), OrgRoleMenuDO::getOrgMenuId);
    }

    @Override
    @Cacheable(value = OrgPermissionCacheKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
    public Set<String> getMenuRoleIdListByMenuIdFromCache(String menuId) {
        return convertSet(orgRoleMenuMapper.selectListByMenuId(menuId), OrgRoleMenuDO::getOrgRoleId);
    }

    // ========== 用户-角色的相关方法  ==========

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    @CacheEvict(value = OrgPermissionCacheKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public void assignUserRole(String userId, Set<String> roleIds, String orgInfoId) {
        // 获得角色拥有角色编号
        Set<String> dbRoleIds = convertSet(orgUserRoleMapper.selectListByUserId(userId),
                OrgUserRoleDO::getOrgRoleId);
        // 计算新增和删除的角色编号
        Set<String> roleIdList = CollUtil.emptyIfNull(roleIds);
        Collection<String> createRoleIds = CollUtil.subtract(roleIdList, dbRoleIds);
        Collection<String> deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIdList);
        // 执行新增和删除。对于已经授权的角色，不用做任何处理
        if (!CollectionUtil.isEmpty(createRoleIds)) {
            orgUserRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
                OrgUserRoleDO entity = new OrgUserRoleDO();
                entity.setOrgUserId(userId);
                entity.setOrgRoleId(roleId);
                if (Objects.nonNull(orgInfoId)) {
                    entity.setOrgInfoId(orgInfoId);
                }
                return entity;
            }));
        }
        if (!CollectionUtil.isEmpty(deleteMenuIds)) {
            orgUserRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds);
        }
    }

    @Override
    @CacheEvict(value = OrgPermissionCacheKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public void processUserDeleted(String userId) {
        orgUserRoleMapper.deleteListByUserId(userId);
    }

    @Override
    public Set<String> getUserRoleIdListByUserId(String userId) {
        return convertSet(orgUserRoleMapper.selectListByUserId(userId), OrgUserRoleDO::getOrgRoleId);
    }

    @Override
    @Cacheable(value = OrgPermissionCacheKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public Set<String> getUserRoleIdListByUserIdFromCache(String userId) {
        return getUserRoleIdListByUserId(userId);
    }

    @Override
    public Set<String> getUserRoleIdListByRoleId(Collection<String> roleIds) {
        return convertSet(orgUserRoleMapper.selectListByRoleIds(roleIds), OrgUserRoleDO::getOrgUserId);
    }

    /**
     * 获得用户拥有的角色，并且这些角色是开启状态的
     *
     * @param userId 用户编号
     * @return 用户拥有的角色
     */
    @VisibleForTesting
    List<OrgRoleDO> getEnableUserRoleListByUserIdFromCache(String userId) {
        // 获得用户拥有的角色编号
        Set<String> roleIds = getSelf().getUserRoleIdListByUserIdFromCache(userId);
        // 获得角色数组，并移除被禁用的
        List<OrgRoleDO> roles = orgRoleService.getRoleListFromCache(roleIds);
        roles.removeIf(role -> !CommonStatusEnum.ENABLE.equals(role.getOrgRoleStatus()));
        return roles;
    }

    // ========== 用户-部门的相关方法  ==========

    @Override
    public void assignRoleDataScope(String roleId, OrgDataScopeEnum dataScope, Set<String> dataScopeDeptIds) {
        orgRoleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds);
    }

    @Override
    @DataPermission(enable = false) // 关闭数据权限，不然就会出现递归获取数据权限的问题
    public OrgDeptDataPermissionRespDTO getDeptDataPermission(String userId) {
        // 获得用户的角色
        List<OrgRoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);

        // 如果角色为空，则只能查看自己
        OrgDeptDataPermissionRespDTO result = new OrgDeptDataPermissionRespDTO();
        if (CollUtil.isEmpty(roles)) {
            result.setSelf(YesOrNoEnum.YES);
            return result;
        }

        // 获得用户的部门编号的缓存，通过 Guava 的 Suppliers 惰性求值，即有且仅有第一次发起 DB 的查询
        Supplier<String> userDeptId = Suppliers.memoize(() -> orgPermissionFacade.getDeptIdByUserId(userId));
        // 遍历每个角色，计算
        for (OrgRoleDO role : roles) {
            // 为空时，跳过
            if (role.getOrgRoleDataScope() == null) {
                continue;
            }
            // 情况一，ALL
            if (Objects.equals(role.getOrgRoleDataScope(), OrgDataScopeEnum.ALL)) {
                result.setAll(YesOrNoEnum.YES);
                continue;
            }
            // 情况二，DEPT_CUSTOM
            if (Objects.equals(role.getOrgRoleDataScope(), OrgDataScopeEnum.DEPT_CUSTOM)) {
                CollUtil.addAll(result.getDeptIds(), role.getOrgRoleDataScopeDeptIds());
                // 自定义可见部门时，保证可以看到自己所在的部门。否则，一些场景下可能会有问题。
                // 例如说，登录时，基于 t_user 的 username 查询会可能被 dept_id 过滤掉
                CollUtil.addAll(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况三，DEPT_ONLY
            if (Objects.equals(role.getOrgRoleDataScope(), OrgDataScopeEnum.DEPT_ONLY)) {
                CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况四，DEPT_DEPT_AND_CHILD
            if (Objects.equals(role.getOrgRoleDataScope(), OrgDataScopeEnum.DEPT_AND_CHILD)) {
                CollUtil.addAll(result.getDeptIds(), orgPermissionFacade.getChildDeptIdListFromCache(userDeptId.get()));
                // 添加本身部门编号
                CollUtil.addAll(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况五，SELF
            if (Objects.equals(role.getOrgRoleDataScope(), OrgDataScopeEnum.SELF)) {
                result.setSelf(YesOrNoEnum.YES);
                continue;
            }
            // 未知情况，error log 即可
            log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, toJsonString(result));
        }
        return result;
    }

    @Override
    public String createRole(OrgRoleSaveReq createReqVO) {
        return orgRoleService.createRole(createReqVO);
    }

    @Override
    public String createRole(SysOrgRoleCreateReq createReq) {
        return orgRoleService.createRole(createReq);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, String> presupposeRole(String orgInfoId) {
        return orgRoleService.presupposeRole(orgInfoId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void assignDefaultRoleMenu(String orgInfoId,Map<String, String> menuMap, Map<String, String> roleMap) {
        List<SysTemplateRoleMenuResp> roleMenuList = sysTemplatePermissionService.getRoleMenuList();
        if (CollUtil.isEmpty(roleMenuList)){
            throw exception(ORG_TEMPLATE_ROLE_MENU_IS_NULL);
        }
        Map<String, List<SysTemplateRoleMenuResp>> roleMenuMap = roleMenuList.stream().collect(Collectors.groupingBy(SysTemplateRoleMenuResp::getSysRoleId));
        List<OrgRoleMenuDO> orgRoleMenus=new ArrayList<>();
        OrgRoleMenuDO orgRoleMenu;
        for (String sysRoleId:roleMenuMap.keySet()){
            for (SysTemplateRoleMenuResp sysRoleMenu:roleMenuMap.get(sysRoleId)){
                orgRoleMenu=new OrgRoleMenuDO();
                orgRoleMenu.setOrgRoleId(roleMap.get(sysRoleId));
                orgRoleMenu.setOrgMenuId(menuMap.get(sysRoleMenu.getSysMenuId()));
                orgRoleMenu.setOrgInfoId(orgInfoId);
                orgRoleMenus.add(orgRoleMenu);
            }
        }
        orgRoleMenuMapper.insertBatch(orgRoleMenus);
    }

    @Override
    public void updateRole(OrgRoleUpdateReq updateReqVO) {
         orgRoleService.updateRole(updateReqVO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteRole(String roleId) {
        orgRoleService.deleteRole(roleId);
        // 删除相关数据
        processRoleDeleted(roleId);
    }

    @Override
    public void updateRoleStatus(String roleId, CommonStatusEnum status) {
        orgRoleService.updateRoleStatus(roleId,status);
    }

    @Override
    public void updateRoleDataScope(String roleId, OrgDataScopeEnum dataScope, Set<String> dataScopeDeptIds) {
        orgRoleService.updateRoleDataScope(roleId,dataScope,dataScopeDeptIds);
    }

    @Override
    public OrgRoleDO getRole(String roleId) {
        return orgRoleService.getRole(roleId);
    }

    @Override
    public OrgRoleDO getRoleFromCache(String roleId) {
        return orgRoleService.getRoleFromCache(roleId);
    }

    @Override
    public List<OrgRoleDO> getRoleList(Collection<String> roleIds) {
        return orgRoleService.getRoleList(roleIds);
    }

    @Override
    public List<OrgRoleDO> getRoleListFromCache(Collection<String> roleIds) {
        return orgRoleService.getRoleListFromCache(roleIds);
    }

    @Override
    public List<OrgRoleDO> getRoleListByStatus(Collection<CommonStatusEnum> statuses) {
        return orgRoleService.getRoleListByStatus(statuses);
    }

    @Override
    public List<OrgRoleDO> getRoleList() {
        return orgRoleService.getRoleList();
    }

    @Override
    public PageResult<OrgRoleDO> getRolePage(OrgRolePageReq reqVO) {
        return orgRoleService.getRolePage(reqVO);
    }

    @Override
    public boolean hasAnySuperAdmin(Collection<String> roleIds) {
        return orgRoleService.hasAnySuperAdmin(roleIds);
    }

    @Override
    public void validateRoleList(Collection<String> roleIds) {
        orgRoleService.validateRoleList(roleIds);
    }

    @Override
    public String createMenu(OrgMenuSaveReq createReqVO) {
        return orgMenuService.createMenu(createReqVO);
    }

    @Override
    public void updateMenu(OrgMenuSaveReq updateReqVO) {
        orgMenuService.updateMenu(updateReqVO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteMenu(String menuId) {
        orgMenuService.deleteMenu(menuId);
        processMenuDeleted(menuId);
    }

    @Override
    public List<OrgMenuDO> getMenuList() {
        return orgMenuService.getMenuList();
    }

    @Override
    public List<OrgMenuDO> getMenuListByTenant(OrgMenuListReq reqVO) {
        return orgMenuService.getMenuListByTenant(reqVO);
    }

    @Override
    public List<OrgMenuDO> getMenuList(OrgMenuListReq reqVO) {
        return orgMenuService.getMenuList(reqVO);
    }

    @Override
    public List<String> getMenuIdListByPermissionFromCache(String permission) {
        return orgMenuService.getMenuIdListByPermissionFromCache(permission);
    }

    @Override
    public OrgMenuDO getMenu(String menuId) {
        return orgMenuService.getMenu(menuId);
    }

    @Override
    public List<OrgMenuDO> getMenuList(Collection<String> menuIds) {
        return orgMenuService.getMenuList(menuIds);
    }

    @Override
    public Long countAdmin() {
        return orgRoleService.countAdmin();
    }

    @Override
    public List<OrgRoleSimpleResp> getByOrgUserId(String orgUserId) {
        if (StrUtil.isBlank(orgUserId)){
            return List.of();
        }
        List<String> roleIds = orgUserRoleMapper.getByOrgUserId(orgUserId);
        if(CollUtil.isEmpty(roleIds)){
            return List.of();
        }
        return orgRoleService.getRoleSimpleList(roleIds);
    }

    @Override
    public List<OrgRoleDO> getRoleListByOrgInfoIdAndRoleCode(String orgInfoId, OrgRoleCodeEnum orgRoleCode) {
        return orgRoleMapper.getRoleListByOrgInfoIdAndRoleCode(orgInfoId,orgRoleCode);
    }

    @Override
    public Map<String,String> initOrgInfoDefaultMenus(String orgInfoId) {
       return this.orgMenuService.initMenuListByOrgInfoId(orgInfoId);
    }

    @Override
    public void assignRoleMenuByOrgInfoId(String orgInfoId, String orgRoleAdminId, List<String> menuIds) {
        // 获得角色拥有菜单编号
        Set<String> dbMenuIds = convertSet(orgRoleMenuMapper.selectListByRoleId(orgRoleAdminId), OrgRoleMenuDO::getOrgMenuId);
        // 计算新增和删除的菜单编号
        List<String> menuIdList = CollUtil.emptyIfNull(menuIds);
        Collection<String> createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds);
        Collection<String> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList);
        // 执行新增和删除。对于已经授权的菜单，不用做任何处理
        if (CollUtil.isNotEmpty(createMenuIds)) {
            orgRoleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
                OrgRoleMenuDO entity = new OrgRoleMenuDO();
                entity.setOrgRoleId(orgRoleAdminId);
                entity.setOrgMenuId(menuId);
                entity.setOrgInfoId(orgInfoId);
                return entity;
            }));
        }
        if (CollUtil.isNotEmpty(deleteMenuIds)) {
            orgRoleMenuMapper.deleteListByRoleIdAndMenuIds(orgRoleAdminId, deleteMenuIds);
        }
    }

    @Override
    public void excelTemplate(HttpServletResponse response) throws IOException {
        ClassPathResource resource = new ClassPathResource(TEMPLATE_EXCEL);
        InputStream in = resource.getInputStream();

        response.setHeader("Content-disposition", String.format("attachment; filename=\"%s\"", TEMPLATE_EXCEL));
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Pragma", "no-cache");
        response.setDateHeader("Expires", -1);
        response.setCharacterEncoding("UTF-8");
        ServletOutputStream out = response.getOutputStream();

        int len = 0;
        byte[] buffer = new byte[1024];
        while ((len = in.read(buffer)) > 0) {
            out.write(buffer, 0, len);
        }
        in.close();
        out.close();
    }

    @Override
    public OrgPermissionBatchAssignUserRoleResp batchAssignUserRole(String fileUrl) throws Exception {
        // 1.extract config data from excel
        boolean isXlsx = FileTypeUtils.isTypeOf(fileUrl, MimeTypeEnum.XLSX);
        boolean isXls = FileTypeUtils.isTypeOf(fileUrl, MimeTypeEnum.XLS);
        PreconditionsUtils.checkArgument(isXls || isXlsx,OrgPermissionErrorCodeConstants.MIME_TYPE_UNMATCHED);
        byte[] content = fileClientPrivate.getContent(fileUrl);
        ByteArrayInputStream in = new ByteArrayInputStream(content);
        List<OrgPermissionAssignUserRoleRowReq> importReqs = EasyExcel.read(in, OrgPermissionAssignUserRoleRowReq.class,
                        new ExcelListener())
                .autoCloseStream(false)
                .doReadAllSync();
        // validate total rows of excel data
        PreconditionsUtils.checkArgument(importReqs.size() <= 2000 && importReqs.size() > 0, OrgPermissionErrorCodeConstants.BATCH_CREATE_ROW_LIMIT_EXCEEDED,importReqs.size());

        // collect all role-name from excel
        Set<String> roleNameSet = importReqs.stream().map(OrgPermissionAssignUserRoleRowReq::getOrgUserRoleName).collect(Collectors.toSet());

        List<OrgRoleDO> importRoles = orgRoleMapper.listByRoleNames(roleNameSet);

        Map<String, List<OrgRoleDO>> roleNameMap = importRoles.stream().collect(Collectors.groupingBy(OrgRoleDO::getOrgRoleName, Collectors.toList()));

        // enrich field-value by facade-method
        orgUserFacade.fillInUserId(importReqs, OrgPermissionAssignUserRoleRowReq::getOrgUserLoginId, OrgPermissionAssignUserRoleRowReq::setOrgUserId);

        List<OrgPermissionAssignUserRoleImportFailResp> failedRows = new ArrayList<>();

        Map<String, Set<String>> userIdRolesMap = new HashMap<>();
        // pre-check before DB-operation
        for (OrgPermissionAssignUserRoleRowReq importReq : importReqs) {
            // validate if org-user-id exists
            if (StrUtil.isEmpty(importReq.getOrgUserId())) {
                OrgPermissionAssignUserRoleImportFailResp failRow = OrgPermissionAssign.INSTANCE.convert(importReq);
                failRow.setFailReason("当前账号对应的用户不存在");
                failedRows.add(failRow);
            }
            // validate if role-name exists
            else if (!roleNameMap.containsKey(importReq.getOrgUserRoleName())) {
                OrgPermissionAssignUserRoleImportFailResp failRow = OrgPermissionAssign.INSTANCE.convert(importReq);
                failRow.setFailReason("当前配置的角色不存在");
                failedRows.add(failRow);
            } else {
                String orgUserId = importReq.getOrgUserId();
                String roleName = importReq.getOrgUserRoleName();
                Set<String> roleSet;
                if (userIdRolesMap.containsKey(orgUserId)) {
                    roleSet = userIdRolesMap.get(orgUserId);
                } else {
                    roleSet = new HashSet<>();
                    userIdRolesMap.put(orgUserId, roleSet);
                }
                roleSet.add(roleNameMap.get(roleName).get(0).getOrgRoleId());
            }
        }
        // update DB by each user-id
        for (Map.Entry<String, Set<String>> entry : userIdRolesMap.entrySet()) {
            String userId = entry.getKey();
            Set<String> roleSet = entry.getValue();
            assignUserRole(userId, roleSet, null);
        }

        OrgPermissionBatchAssignUserRoleResp resp = OrgPermissionBatchAssignUserRoleResp.builder()
                .count(importReqs.size())
                .failCount(failedRows.size())
                .successCount(importReqs.size() - failedRows.size())
                .importFail(failedRows.size() > 0)
                .build();

        // 5. write failed rows to excel
        if (!failedRows.isEmpty()) {
            String now = DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_MS_PATTERN);
            String fileName = "批量赋予用户角色异常数据-" + now + ".xlsx";
            EasyExcel.write(fileName, OrgPermissionAssignUserRoleImportFailResp.class).sheet().doWrite(failedRows);
            FileInputStream inputStream = new FileInputStream(fileName);
            String res = fileClientPrivate.upload(inputStream.readAllBytes(), fileName, MimeTypeEnum.XLSX.getMimeType());
            resp.setFailFileName(fileName);
            resp.setFailFileUrl(res);
            inputStream.close();
        }

        return resp;
    }

    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private OrgPermissionServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

}
